/*
 * Copyright (c) 2004, 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.org.apache.xerces.internal.jaxp.datatype;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.TimeZone;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Date;
import java.util.Locale;

import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import com.sun.org.apache.xerces.internal.util.DatatypeMessageFormatter;
import com.sun.org.apache.xerces.internal.utils.SecuritySupport;

/**
 * <p>Representation for W3C XML Schema 1.0 date/time datatypes. Specifically, these date/time
 * datatypes are {@link DatatypeConstants#DATETIME dateTime}, {@link DatatypeConstants#TIME time},
 * {@link DatatypeConstants#DATE date}, {@link DatatypeConstants#GYEARMONTH gYearMonth}, {@link
 * DatatypeConstants#GMONTHDAY gMonthDay}, {@link DatatypeConstants#GYEAR gYear}, {@link
 * DatatypeConstants#GMONTH gMonth} and {@link DatatypeConstants#GDAY gDay} defined in the XML
 * Namespace <code>"http://www.w3.org/2001/XMLSchema"</code>. These datatypes are normatively
 * defined in <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">W3C XML Schema 1.0 Part 2,
 * Section 3.2.7-14</a>.</p>
 *
 * <p>The table below defines the mapping between XML Schema 1.0 date/time datatype fields and this
 * class' fields. It also summarizes the value constraints for the date and time fields defined in
 * <a href="http://www.w3.org/TR/xmlschema-2/#isoformats">W3C XML Schema 1.0 Part 2, Appendix D,
 * <i>ISO 8601 Date and Time Formats</i></a>.</p>
 *
 * <a name="datetimefieldsmapping"/> <table border="2" rules="all" cellpadding="2"> <thead> <tr> <th
 * align="center" colspan="3"> Date/time datatype field mapping between XML Schema 1.0 and Java
 * representation </th> </tr> </thead> <tbody> <tr> <th>XML Schema 1.0<br/> datatype<br/> field</th>
 * <th>Related<br/>XMLGregorianCalendar<br/>Accessor(s)</th> <th>Value Range</th> </tr> <a
 * name="datetimefield-year"/> <tr> <td> year </td> <td> {@link #getYear()} + {@link #getEon()}
 * or<br/> {@link #getEonAndYear} </td> <td> <code>getYear()</code> is a value between -(10^9-1) to
 * (10^9)-1 or {@link DatatypeConstants#FIELD_UNDEFINED}.<br/> {@link #getEon()} is high order year
 * value in billion of years.<br/> <code>getEon()</code> has values greater than or equal to (10^9)
 * or less than or equal to -(10^9). A value of null indicates field is undefined.</br> Given that
 * <a href="http://www.w3.org/2001/05/xmlschema-errata#e2-63">XML Schema 1.0 errata</a> states that
 * the year zero will be a valid lexical value in a future version of XML Schema, this class allows
 * the year field to be set to zero. Otherwise, the year field value is handled exactly as described
 * in the errata and [ISO-8601-1988]. Note that W3C XML Schema 1.0 validation does not allow for the
 * year field to have a value of zero. </td> </tr> <a name="datetimefield-month"/> <tr> <td> month
 * </td> <td> {@link #getMonth()} </td> <td> 1 to 12 or {@link DatatypeConstants#FIELD_UNDEFINED}
 * </td> </tr> <a name="datetimefield-day"/> <tr> <td> day </td> <td> {@link #getDay()} </td> <td>
 * Independent of month, max range is 1 to 31 or {@link DatatypeConstants#FIELD_UNDEFINED}.<br/> The
 * normative value constraint stated relative to month field's value is in <a
 * href="http://www.w3.org/TR/xmlschema-2/#isoformats">W3C XML Schema 1.0 Part 2, Appendix D</a>.
 * </td> </tr> <a name="datetimefield-hour"/> <tr> <td> hour </td> <td> {@link #getHour()} </td>
 * <td> 0 to 23 or {@link DatatypeConstants#FIELD_UNDEFINED}. An hour value of 24 is allowed to be
 * set in the lexical space provided the minute and second field values are zero. However, an hour
 * value of 24 is not allowed in value space and will be transformed to represent the value of the
 * first instance of the following day as per <a href="http://www.w3.org/TR/xmlschema-2/#built-in-primitive-datatypes">
 * XML Schema Part 2: Datatypes Second Edition, 3.2 Primitive datatypes</a>. </td> </tr> <a
 * name="datetimefield-minute"/> <tr> <td> minute </td> <td> {@link #getMinute()} </td> <td> 0 to 59
 * or {@link DatatypeConstants#FIELD_UNDEFINED} </td> </tr> <a name="datetimefield-second"/> <tr>
 * <td>second</td> <td> {@link #getSecond()} + {@link #getMillisecond()}/1000 or<br/> {@link
 * #getSecond()} + {@link #getFractionalSecond()} </td> <td> {@link #getSecond()} from 0 to 60 or
 * {@link DatatypeConstants#FIELD_UNDEFINED}.<br/> <i>(Note: 60 only allowable for leap
 * second.)</i><br/> {@link #getFractionalSecond()} allows for infinite precision over the range
 * from 0.0 to 1.0 when the {@link #getSecond()} is defined.<br/> <code>FractionalSecond</code> is
 * optional and has a value of <code>null</code> when it is undefined.<br /> {@link
 * #getMillisecond()} is the convenience millisecond precision of value of {@link
 * #getFractionalSecond()}. </td> </tr> <tr id="datetimefield-timezone"> <td> timezone </td> <td>
 * {@link #getTimezone()} </td> <td> Number of minutes or {@link DatatypeConstants#FIELD_UNDEFINED}.
 * Value range from -14 hours (-14 * 60 minutes) to 14 hours (14 * 60 minutes). </td> </tr> </tbody>
 * </table>
 *
 * <p>All maximum value space constraints listed for the fields in the table above are checked by
 * factory methods, setter methods and parse methods of this class. <code>IllegalArgumentException</code>
 * is thrown when parameter's value is outside the maximum value constraint for the field.
 * Validation checks, for example, whether days in month should be limited to 29, 30 or 31 days,
 * that are dependent on the values of other fields are not checked by these methods. </p>
 *
 * <p>The following operations are defined for this class: <ul> <li>factory methods to create
 * instances</li> <li>accessors/mutators for independent date/time fields</li> <li>conversion
 * between this class and W3C XML Schema 1.0 lexical representation</li> <li>conversion between this
 * class and <code>java.util.GregorianCalendar</code></li> <li>partial order relation comparator
 * method, {@link #compare(XMLGregorianCalendar)}</li> <li>{@link #equals(Object)} defined relative
 * to {@link #compare(XMLGregorianCalendar)}.</li> <li> addition operation with {@link
 * javax.xml.datatype.Duration}. instance as defined in <a href="http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes">
 * W3C XML Schema 1.0 Part 2, Appendix E, <i>Adding durations to dateTimes</i></a>.</li> </ul> </p>
 *
 * @author <a href="mailto:Kohsuke.Kawaguchi@Sun.com">Kohsuke Kawaguchi</a>
 * @author <a href="mailto:Joseph.Fialli@Sun.com">Joseph Fialli</a>
 * @author <a href="mailto:Sunitha.Reddy@Sun.com">Sunitha Reddy</a>
 * @version $Revision: 1.14 $, $Date: 2010-11-10 07:41:41 $
 * @see javax.xml.datatype.Duration
 * @since 1.5
 */

public class XMLGregorianCalendarImpl
    extends XMLGregorianCalendar
    implements Serializable, Cloneable {

  /**
   * <p>Eon of this <code>XMLGregorianCalendar</code>.</p>
   */
  private BigInteger eon = null;

  /**
   * <p>Year of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int year = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Month of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int month = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Day of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int day = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Timezone of this <code>XMLGregorianCalendar</code> in minutes.</p>
   */
  private int timezone = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Hour of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int hour = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Minute of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int minute = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Second of this <code>XMLGregorianCalendar</code>.</p>
   */
  private int second = DatatypeConstants.FIELD_UNDEFINED;

  /**
   * <p>Fractional second of this <code>XMLGregorianCalendar</code>.</p>
   */
  private BigDecimal fractionalSecond = null;

  /**
   * <p>Constant to represent a billion.</p>
   */
  private static final BigInteger BILLION = new BigInteger("1000000000");

  /**
   * <p>Obtain a pure Gregorian Calendar by calling
   * GregorianCalendar.setChange(PURE_GREGORIAN_CHANGE). </p>
   */
  private static final Date PURE_GREGORIAN_CHANGE =
      new Date(Long.MIN_VALUE);

  /**
   * Year index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int YEAR = 0;

  /**
   * Month index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int MONTH = 1;

  /**
   * Day index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int DAY = 2;

  /**
   * Hour index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int HOUR = 3;

  /**
   * Minute index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int MINUTE = 4;

  /**
   * Second index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int SECOND = 5;

  /**
   * Second index for MIN_ and MAX_FIELD_VALUES.
   */
  private static final int MILLISECOND = 6;

  /**
   * Timezone index for MIN_ and MAX_FIELD_VALUES
   */
  private static final int TIMEZONE = 7;


  /**
   * field names indexed by YEAR..TIMEZONE.
   */
  private static final String FIELD_NAME[] = {
      "Year",
      "Month",
      "Day",
      "Hour",
      "Minute",
      "Second",
      "Millisecond",
      "Timezone"
  };

  /**
   * <p>Stream Unique Identifier.</p>
   *
   * <p>TODO: Serialization should use the XML string representation as
   * the serialization format to ensure future compatibility.</p>
   */
  private static final long serialVersionUID = 1L;

  /**
   * <p>Use as a template for default field values when converting to a {@link GregorianCalendar},
   * set to a leap year date of January 1, 0400 at midnight.</p>
   *
   * <p>Fields that are optional for an <code>xsd:dateTime</code> instances are defaulted to not
   * being set to any value. <code>XMLGregorianCalendar</code> fields millisecond, fractional second
   * and timezone return the value indicating that the field is not set, {@link
   * DatatypeConstants#FIELD_UNDEFINED} for millisecond and timezone and <code>null</code> for
   * fractional second.</p>
   *
   * @see #toGregorianCalendar(TimeZone, Locale, XMLGregorianCalendar)
   */
  public static final XMLGregorianCalendar LEAP_YEAR_DEFAULT =
      createDateTime(
          400,  //year
          DatatypeConstants.JANUARY,  //month
          1,  // day
          0,  // hour
          0,  // minute
          0,  // second
          DatatypeConstants.FIELD_UNDEFINED,  // milliseconds
          DatatypeConstants.FIELD_UNDEFINED // timezone
      );

  // Constructors

  /**
   * Constructs a new XMLGregorianCalendar object.
   *
   * String parsing documented by {@link #parse(String)}.
   *
   * Returns a non-null valid XMLGregorianCalendar object that holds the
   * value indicated by the lexicalRepresentation parameter.
   *
   * @param lexicalRepresentation Lexical representation of one the eight XML Schema date/time
   * datatypes.
   * @throws IllegalArgumentException If the given string does not conform as documented in {@link
   * #parse(String)}.
   * @throws NullPointerException If the given string is null.
   */
  protected XMLGregorianCalendarImpl(String lexicalRepresentation)
      throws IllegalArgumentException {

    // compute format string for this lexical representation.
    String format = null;
    String lexRep = lexicalRepresentation;
    final int NOT_FOUND = -1;
    int lexRepLength = lexRep.length();

    // current parser needs a format string,
    // use following heuristics to figure out what xml schema date/time
    // datatype this lexical string could represent.
    // Fix 4971612: invalid SCCS macro substitution in data string,
    //   no %{alpha}% to avoid SCCS maco substitution
    if (lexRep.indexOf('T') != NOT_FOUND) {
      // found Date Time separater, must be xsd:DateTime
      format = "%Y-%M-%DT%h:%m:%s" + "%z";
    } else if (lexRepLength >= 3 && lexRep.charAt(2) == ':') {
      // found ":", must be xsd:Time
      format = "%h:%m:%s" + "%z";
    } else if (lexRep.startsWith("--")) {
      // check for gDay || gMonth || gMonthDay
      if (lexRepLength >= 3 && lexRep.charAt(2) == '-') {
        // gDay, ---DD(z?)
        format = "---%D" + "%z";
      } else if (lexRepLength == 4     // --MM
          || lexRepLength == 5     // --MMZ
          || lexRepLength == 10) { // --MMSHH:MM
        // gMonth, --MM(z?),
        // per XML Schema Errata, used to be --MM--(z?)
        format = "--%M" + "%z";
      } else {
        // gMonthDay, --MM-DD(z?), (or invalid lexicalRepresentation)
        // length should be:
        //  7: --MM-DD
        //  8: --MM-DDZ
        // 13: --MM-DDSHH:MM
        format = "--%M-%D" + "%z";
      }
    } else {
      // check for Date || GYear | GYearMonth
      int countSeparator = 0;

      // start at index 1 to skip potential negative sign for year.

      int timezoneOffset = lexRep.indexOf(':');
      if (timezoneOffset != NOT_FOUND) {

        // found timezone, strip it off for distinguishing
        // between Date, GYear and GYearMonth so possible
        // negative sign in timezone is not mistaken as
        // a separator.
        lexRepLength -= 6;
      }

      for (int i = 1; i < lexRepLength; i++) {
        if (lexRep.charAt(i) == '-') {
          countSeparator++;
        }
      }
      if (countSeparator == 0) {
        // GYear
        format = "%Y" + "%z";
      } else if (countSeparator == 1) {
        // GYearMonth
        format = "%Y-%M" + "%z";
      } else {
        // Date or invalid lexicalRepresentation
        // Fix 4971612: invalid SCCS macro substitution in data string
        format = "%Y-%M-%D" + "%z";
      }
    }
    Parser p = new Parser(format, lexRep);
    p.parse();

    // check for validity
    if (!isValid()) {
      throw new IllegalArgumentException(
          DatatypeMessageFormatter
              .formatMessage(null, "InvalidXGCRepresentation", new Object[]{lexicalRepresentation})
          //"\"" + lexicalRepresentation + "\" is not a valid representation of an XML Gregorian Calendar value."
      );
    }
  }

  /**
   * <p>Create an instance with all date/time datatype fields set to
   * {@link DatatypeConstants#FIELD_UNDEFINED} or null respectively.</p>
   */
  public XMLGregorianCalendarImpl() {

    // field initializers already do the correct initialization.
  }

  /**
   * <p>Private constructor allowing for complete value spaces allowed by
   * W3C XML Schema 1.0 recommendation for xsd:dateTime and related
   * builtin datatypes. Note that <code>year</code> parameter supports
   * arbitrarily large numbers and fractionalSecond has infinite
   * precision.</p>
   *
   * @param year of <code>XMLGregorianCalendar</code> to be created.
   * @param month of <code>XMLGregorianCalendar</code> to be created.
   * @param day of <code>XMLGregorianCalendar</code> to be created.
   * @param hour of <code>XMLGregorianCalendar</code> to be created.
   * @param minute of <code>XMLGregorianCalendar</code> to be created.
   * @param second of <code>XMLGregorianCalendar</code> to be created.
   * @param fractionalSecond of <code>XMLGregorianCalendar</code> to be created.
   * @param timezone of <code>XMLGregorianCalendar</code> to be created.
   */
  protected XMLGregorianCalendarImpl(
      BigInteger year,
      int month,
      int day,
      int hour,
      int minute,
      int second,
      BigDecimal fractionalSecond,
      int timezone) {

    setYear(year);
    setMonth(month);
    setDay(day);
    setTime(hour, minute, second, fractionalSecond);
    setTimezone(timezone);

    // check for validity
    if (!isValid()) {

      throw new IllegalArgumentException(
          DatatypeMessageFormatter.formatMessage(null,
              "InvalidXGCValue-fractional",
              new Object[]{year, new Integer(month), new Integer(day),
                  new Integer(hour), new Integer(minute), new Integer(second),
                  fractionalSecond, new Integer(timezone)})
      );

      /**
       String yearString = "null";
       if (year != null) {
       yearString = year.toString();
       }
       String fractionalSecondString = "null";
       if (fractionalSecond != null) {
       fractionalSecondString = fractionalSecond.toString();
       }

       throw new IllegalArgumentException(
       "year = " + yearString
       + ", month = " + month
       + ", day = " + day
       + ", hour = " + hour
       + ", minute = " + minute
       + ", second = " + second
       + ", fractionalSecond = " + fractionalSecondString
       + ", timezone = " + timezone
       + ", is not a valid representation of an XML Gregorian Calendar value."
       );
       */

    }

  }

  /**
   * <p>Private constructor of value spaces that a
   * <code>java.util.GregorianCalendar</code> instance would need to convert to an
   * <code>XMLGregorianCalendar</code> instance.</p>
   *
   * <p><code>XMLGregorianCalendar eon</code> and
   * <code>fractionalSecond</code> are set to <code>null</code></p>
   *
   * @param year of <code>XMLGregorianCalendar</code> to be created.
   * @param month of <code>XMLGregorianCalendar</code> to be created.
   * @param day of <code>XMLGregorianCalendar</code> to be created.
   * @param hour of <code>XMLGregorianCalendar</code> to be created.
   * @param minute of <code>XMLGregorianCalendar</code> to be created.
   * @param second of <code>XMLGregorianCalendar</code> to be created.
   * @param millisecond of <code>XMLGregorianCalendar</code> to be created.
   * @param timezone of <code>XMLGregorianCalendar</code> to be created.
   */
  private XMLGregorianCalendarImpl(
      int year,
      int month,
      int day,
      int hour,
      int minute,
      int second,
      int millisecond,
      int timezone) {

    setYear(year);
    setMonth(month);
    setDay(day);
    setTime(hour, minute, second);
    setTimezone(timezone);
    setMillisecond(millisecond);

    if (!isValid()) {

      throw new IllegalArgumentException(
          DatatypeMessageFormatter.formatMessage(null,
              "InvalidXGCValue-milli",
              new Object[]{new Integer(year), new Integer(month), new Integer(day),
                  new Integer(hour), new Integer(minute), new Integer(second),
                  new Integer(millisecond), new Integer(timezone)})
      );
                /*
                throw new IllegalArgumentException(
                    "year = " + year
                    + ", month = " + month
                    + ", day = " + day
                    + ", hour = " + hour
                    + ", minute = " + minute
                    + ", second = " + second
                    + ", millisecond = " + millisecond
                    + ", timezone = " + timezone
                    + ", is not a valid representation of an XML Gregorian Calendar value."
                    );
                 */

    }
  }

  /**
   * <p>Convert a <code>java.util.GregorianCalendar</code> to XML Schema 1.0
   * representation.</p>
   *
   * <table border="2" rules="all" cellpadding="2">
   * <thead>
   * <tr>
   * <th align="center" colspan="2">
   * Field by Field Conversion from
   * <code>java.util.GregorianCalendar</code> to this class
   * </th>
   * </tr>
   * </thead>
   * <tbody>
   * <tr>
   * <th><code>javax.xml.datatype.XMLGregorianCalendar</code> field</th>
   * <th><code>java.util.GregorianCalendar</code> field</th>
   * </tr>
   * <tr>
   * <th>{@link #setYear(int)}</th>
   * <th><code>ERA == GregorianCalendar.BC ? -YEAR : YEAR</code></th>
   * </tr>
   * <tr>
   * <th>{@link #setMonth(int)}</th>
   * <th><code>MONTH + 1</code></th>
   * </tr>
   * <tr>
   * <th>{@link #setDay(int)}</th>
   * <th><code>DAY_OF_MONTH</code></th>
   * </tr>
   * <tr>
   * <th>{@link #setTime(int, int, int, BigDecimal)}</th>
   * <th><code>HOUR_OF_DAY, MINUTE, SECOND, MILLISECOND</code></th>
   * </tr>
   * <tr>
   * <th>{@link #setTimezone(int)}<i>*</i></th>
   * <th><code>(ZONE_OFFSET + DST_OFFSET) / (60*1000)</code><br/>
   * <i>(in minutes)</i>
   * </th>
   * </tr>
   * </tbody>
   * </table>
   * <p><i>*</i>conversion loss of information. It is not possible to represent
   * a <code>java.util.GregorianCalendar</code> daylight savings timezone id in the
   * XML Schema 1.0 date/time datatype representation.</p>
   *
   * <p>To compute the return value's <code>TimeZone</code> field,
   * <ul>
   * <li>when <code>this.getTimezone() != DatatypeConstants.FIELD_UNDEFINED</code>,
   * create a <code>java.util.TimeZone</code> with a custom timezone id
   * using the <code>this.getTimezone()</code>.</li>
   * <li>else use the <code>GregorianCalendar</code> default timezone value
   * for the host is defined as specified by
   * <code>java.util.TimeZone.getDefault()</code>.</li></p>
   *
   * @param cal <code>java.util.GregorianCalendar</code> used to create
   * <code>XMLGregorianCalendar</code>
   */
  public XMLGregorianCalendarImpl(GregorianCalendar cal) {

    int year = cal.get(Calendar.YEAR);
    if (cal.get(Calendar.ERA) == GregorianCalendar.BC) {
      year = -year;
    }
    this.setYear(year);

    // Calendar.MONTH is zero based, XSD Date datatype's month field starts
    // with JANUARY as 1.
    this.setMonth(cal.get(Calendar.MONTH) + 1);
    this.setDay(cal.get(Calendar.DAY_OF_MONTH));
    this.setTime(
        cal.get(Calendar.HOUR_OF_DAY),
        cal.get(Calendar.MINUTE),
        cal.get(Calendar.SECOND),
        cal.get(Calendar.MILLISECOND));

    // Calendar ZONE_OFFSET and DST_OFFSET fields are in milliseconds.
    int offsetInMinutes =
        (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET)) / (60 * 1000);
    this.setTimezone(offsetInMinutes);
  }

  // Factories

  /**
   * <p>Create a Java representation of XML Schema builtin datatype <code>dateTime</code>.
   * All possible fields are specified for this factory method.</p>
   *
   * @param year represents both high-order eons and low-order year.
   * @param month of <code>dateTime</code>
   * @param day of <code>dateTime</code>
   * @param hours of <code>dateTime</code>
   * @param minutes of <code>dateTime</code>
   * @param seconds of <code>dateTime</code>
   * @param fractionalSecond value of null indicates optional field is absent.
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createDateTime(
      BigInteger year,
      int month,
      int day,
      int hours,
      int minutes,
      int seconds,
      BigDecimal fractionalSecond,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        year,
        month,
        day,
        hours,
        minutes,
        seconds,
        fractionalSecond,
        timezone);
  }

  /**
   * <p>Create a Java instance of XML Schema builtin datatype dateTime.</p>
   *
   * @param year represents both high-order eons and low-order year.
   * @param month of <code>dateTime</code>
   * @param day of <code>dateTime</code>
   * @param hour of <code>dateTime</code>
   * @param minute of <code>dateTime</code>
   * @param second of <code>dateTime</code>
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createDateTime(
      int year,
      int month,
      int day,
      int hour,
      int minute,
      int second) {

    return new XMLGregorianCalendarImpl(
        year,
        month,
        day,
        hour,
        minute,
        second,
        DatatypeConstants.FIELD_UNDEFINED,  //millisecond
        DatatypeConstants.FIELD_UNDEFINED //timezone
    );
  }

  /**
   * <p>Create a Java representation of XML Schema builtin datatype <code>dateTime</code>.
   * All possible fields are specified for this factory method.</p>
   *
   * @param year represents low-order year.
   * @param month of <code>dateTime</code>
   * @param day of <code>dateTime</code>
   * @param hours of <code>dateTime</code>
   * @param minutes of <code>dateTime</code>
   * @param seconds of <code>dateTime</code>
   * @param milliseconds of <code>dateTime</code>. {@link DatatypeConstants#FIELD_UNDEFINED}
   * indicates optional field is not set.
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createDateTime(
      int year,
      int month,
      int day,
      int hours,
      int minutes,
      int seconds,
      int milliseconds,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        year,
        month,
        day,
        hours,
        minutes,
        seconds,
        milliseconds,
        timezone);
  }

  /**
   * <p>Create a Java representation of XML Schema builtin datatype <code>date</code> or
   * <code>g*</code>.</p>
   *
   * <p>For example, an instance of <code>gYear</code> can be created invoking this factory with
   * <code>month</code> and <code>day</code> parameters set to {@link
   * DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * @param year of <code>XMLGregorianCalendar</code> to be created.
   * @param month of <code>XMLGregorianCalendar</code> to be created.
   * @param day of <code>XMLGregorianCalendar</code> to be created.
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createDate(
      int year,
      int month,
      int day,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        year,
        month,
        day,
        DatatypeConstants.FIELD_UNDEFINED, // hour
        DatatypeConstants.FIELD_UNDEFINED, // minute
        DatatypeConstants.FIELD_UNDEFINED, // second
        DatatypeConstants.FIELD_UNDEFINED, // millisecond
        timezone);
  }

  /**
   * Create a Java instance of XML Schema builtin datatype <code>time</code>.
   *
   * @param hours number of hours
   * @param minutes number of minutes
   * @param seconds number of seconds
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createTime(
      int hours,
      int minutes,
      int seconds,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        DatatypeConstants.FIELD_UNDEFINED, // Year
        DatatypeConstants.FIELD_UNDEFINED, // Month
        DatatypeConstants.FIELD_UNDEFINED, // Day
        hours,
        minutes,
        seconds,
        DatatypeConstants.FIELD_UNDEFINED, //Millisecond
        timezone);
  }

  /**
   * <p>Create a Java instance of XML Schema builtin datatype time.</p>
   *
   * @param hours number of hours
   * @param minutes number of minutes
   * @param seconds number of seconds
   * @param fractionalSecond value of <code>null</code> indicates that this optional field is not
   * set.
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createTime(
      int hours,
      int minutes,
      int seconds,
      BigDecimal fractionalSecond,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        null,            // Year
        DatatypeConstants.FIELD_UNDEFINED, // month
        DatatypeConstants.FIELD_UNDEFINED, // day
        hours,
        minutes,
        seconds,
        fractionalSecond,
        timezone);
  }

  /**
   * <p>Create a Java instance of XML Schema builtin datatype time.</p>
   *
   * @param hours number of hours
   * @param minutes number of minutes
   * @param seconds number of seconds
   * @param milliseconds number of milliseconds
   * @param timezone offset in minutes. {@link DatatypeConstants#FIELD_UNDEFINED} indicates optional
   * field is not set.
   * @return <code>XMLGregorianCalendar</code> created from parameter values.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see DatatypeConstants#FIELD_UNDEFINED
   */
  public static XMLGregorianCalendar createTime(
      int hours,
      int minutes,
      int seconds,
      int milliseconds,
      int timezone) {

    return new XMLGregorianCalendarImpl(
        DatatypeConstants.FIELD_UNDEFINED, // year
        DatatypeConstants.FIELD_UNDEFINED, // month
        DatatypeConstants.FIELD_UNDEFINED, // day
        hours,
        minutes,
        seconds,
        milliseconds,
        timezone);
  }

  // Accessors

  /**
   * <p>Return high order component for XML Schema 1.0 dateTime datatype field for
   * <code>year</code>.
   * <code>null</code> if this optional part of the year field is not defined.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-year">year field of date/time field mapping table</a>.</p>
   *
   * @return eon of this <code>XMLGregorianCalendar</code>. The value returned is an integer
   * multiple of 10^9.
   * @see #getYear()
   * @see #getEonAndYear()
   */
  public BigInteger getEon() {
    return eon;
  }

  /**
   * <p>Return low order component for XML Schema 1.0 dateTime datatype field for
   * <code>year</code> or {@link DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-year">year field of date/time field mapping table</a>.</p>
   *
   * @return year  of this <code>XMLGregorianCalendar</code>.
   * @see #getEon()
   * @see #getEonAndYear()
   */
  public int getYear() {
    return year;
  }

  /**
   * <p>Return XML Schema 1.0 dateTime datatype field for
   * <code>year</code>.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-year">year field of date/time field mapping table</a>.</p>
   *
   * @return sum of <code>eon</code> and <code>BigInteger.valueOf(year)</code> when both fields are
   * defined. When only <code>year</code> is defined, return it. When both <code>eon</code> and
   * <code>year</code> are not defined, return <code>null</code>.
   * @see #getEon()
   * @see #getYear()
   */
  public BigInteger getEonAndYear() {

    // both are defined
    if (year != DatatypeConstants.FIELD_UNDEFINED
        && eon != null) {

      return eon.add(BigInteger.valueOf((long) year));
    }

    // only year is defined
    if (year != DatatypeConstants.FIELD_UNDEFINED
        && eon == null) {

      return BigInteger.valueOf((long) year);
    }

    // neither are defined
    // or only eon is defined which is not valid without a year
    return null;
  }

  /**
   * <p>Return number of month or {@link DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-month">month field of date/time field mapping table</a>.</p>
   *
   * @return year  of this <code>XMLGregorianCalendar</code>.
   */
  public int getMonth() {
    return month;
  }

  /**
   * Return day in month or {@link DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-day">day field of date/time field mapping table</a>.</p>
   *
   * @see #setDay(int)
   */
  public int getDay() {
    return day;
  }

  /**
   * Return timezone offset in minutes or
   * {@link DatatypeConstants#FIELD_UNDEFINED} if this optional field is not defined.
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-timezone">timezone field of date/time field mapping table</a>.</p>
   *
   * @see #setTimezone(int)
   */
  public int getTimezone() {
    return timezone;
  }

  /**
   * Return hours or {@link DatatypeConstants#FIELD_UNDEFINED}.
   * Returns {@link DatatypeConstants#FIELD_UNDEFINED} if this field is not defined.
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-hour">hour field of date/time field mapping table</a>.</p>
   *
   * @see #setTime(int, int, int)
   */
  public int getHour() {
    return hour;
  }

  /**
   * Return minutes or {@link DatatypeConstants#FIELD_UNDEFINED}.<\p>
   * Returns {@link DatatypeConstants#FIELD_UNDEFINED} if this field is not defined.
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-minute">minute field of date/time field mapping table</a>.</p>
   *
   * @see #setTime(int, int, int)
   */
  public int getMinute() {
    return minute;
  }

  /**
   * <p>Return seconds or {@link DatatypeConstants#FIELD_UNDEFINED}.<\p>
   *
   * <p>Returns {@link DatatypeConstants#FIELD_UNDEFINED} if this field is not defined.
   * When this field is not defined, the optional xs:dateTime
   * fractional seconds field, represented by
   * {@link #getFractionalSecond()} and {@link #getMillisecond()},
   * must not be defined.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-second">second field of date/time field mapping table</a>.</p>
   *
   * @return Second  of this <code>XMLGregorianCalendar</code>.
   * @see #getFractionalSecond()
   * @see #getMillisecond()
   * @see #setTime(int, int, int)
   */
  public int getSecond() {
    return second;
  }

  /**
   * @return result of adding second and fractional second field
   */
  private BigDecimal getSeconds() {
    if (second == DatatypeConstants.FIELD_UNDEFINED) {
      return DECIMAL_ZERO;
    }
    BigDecimal result = BigDecimal.valueOf((long) second);
    if (fractionalSecond != null) {
      return result.add(fractionalSecond);
    } else {
      return result;
    }
  }


  /**
   * <p>Return millisecond precision of {@link #getFractionalSecond()}.<\p>
   *
   * <p>This method represents a convenience accessor to infinite
   * precision fractional second value returned by
   * {@link #getFractionalSecond()}. The returned value is the rounded
   * down to milliseconds value of
   * {@link #getFractionalSecond()}. When {@link #getFractionalSecond()}
   * returns <code>null</code>, this method must return
   * {@link DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * <p>Value constraints for this value are summarized in
   * <a href="#datetimefield-second">second field of date/time field mapping table</a>.</p>
   *
   * @return Millisecond  of this <code>XMLGregorianCalendar</code>.
   * @see #getFractionalSecond()
   * @see #setTime(int, int, int)
   */
  public int getMillisecond() {
    if (fractionalSecond == null) {
      return DatatypeConstants.FIELD_UNDEFINED;
    } else {
      // TODO: Non-optimal solution for now.
      // Efficient implementation would only store as BigDecimal
      // when needed and millisecond otherwise.
      return fractionalSecond.movePointRight(3).intValue();
    }
  }

  /**
   * <p>Return fractional seconds.</p>
   *
   * <p><code>null</code> is returned when this optional field is not defined.</p>
   *
   * <p>Value constraints are detailed in
   * <a href="#datetimefield-second">second field of date/time field mapping table</a>.</p>
   *
   * <p>This optional field can only have a defined value when the
   * xs:dateTime second field, represented by ({@link #getSecond()},
   * does not return {@link DatatypeConstants#FIELD_UNDEFINED}).</p>
   *
   * @return fractional seconds  of this <code>XMLGregorianCalendar</code>.
   * @see #getSecond()
   * @see #setTime(int, int, int, BigDecimal)
   */
  public BigDecimal getFractionalSecond() {
    return fractionalSecond;
  }

  // setters

  /**
   * <p>Set low and high order component of XSD <code>dateTime</code> year field.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of <code>null</code>.</p>
   *
   * @param year value constraints summarized in <a href="#datetimefield-year">year field of
   * date/time field mapping table</a>.
   * @throws IllegalArgumentException if <code>year</code> parameter is outside value constraints
   * for the field as specified in <a href="#datetimefieldmapping">date/time field mapping
   * table</a>.
   */
  public void setYear(BigInteger year) {
    if (year == null) {
      this.eon = null;
      this.year = DatatypeConstants.FIELD_UNDEFINED;
    } else {
      BigInteger temp = year.remainder(BILLION);
      this.year = temp.intValue();
      setEon(year.subtract(temp));
    }
  }

  /**
   * <p>Set year of XSD <code>dateTime</code> year field.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of
   * {@link DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * <p>Note: if the absolute value of the <code>year</code> parameter
   * is less than 10^9, the eon component of the XSD year field is set to
   * <code>null</code> by this method.</p>
   *
   * @param year value constraints are summarized in <a href="#datetimefield-year">year field of
   * date/time field mapping table</a>. If year is {@link DatatypeConstants#FIELD_UNDEFINED}, then
   * eon is set to <code>null</code>.
   */
  public void setYear(int year) {
    if (year == DatatypeConstants.FIELD_UNDEFINED) {
      this.year = DatatypeConstants.FIELD_UNDEFINED;
      this.eon = null;
    } else if (Math.abs(year) < BILLION.intValue()) {
      this.year = year;
      this.eon = null;
    } else {
      BigInteger theYear = BigInteger.valueOf((long) year);
      BigInteger remainder = theYear.remainder(BILLION);
      this.year = remainder.intValue();
      setEon(theYear.subtract(remainder));
    }
  }

  /**
   * <p>Set high order part of XSD <code>dateTime</code> year field.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of
   * <code>null</code>.</p>
   *
   * @param eon value constraints summarized in <a href="#datetimefield-year">year field of
   * date/time field mapping table</a>.
   */
  private void setEon(BigInteger eon) {
    if (eon != null && eon.compareTo(BigInteger.ZERO) == 0) {
      // Treat ZERO as field being undefined.
      this.eon = null;
    } else {
      this.eon = eon;
    }
  }

  /**
   * <p>Set month.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of {@link
   * DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * @param month value constraints summarized in <a href="#datetimefield-month">month field of
   * date/time field mapping table</a>.
   * @throws IllegalArgumentException if <code>month</code> parameter is outside value constraints
   * for the field as specified in <a href="#datetimefieldmapping">date/time field mapping
   * table</a>.
   */
  public void setMonth(int month) {
    if (month < DatatypeConstants.JANUARY || DatatypeConstants.DECEMBER < month) {
      if (month != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(MONTH, month);
      }
    }
    this.month = month;
  }

  /**
   * <p>Set days in month.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of {@link
   * DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * @param day value constraints summarized in <a href="#datetimefield-day">day field of date/time
   * field mapping table</a>.
   * @throws IllegalArgumentException if <code>day</code> parameter is outside value constraints for
   * the field as specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   */
  public void setDay(int day) {
    if (day < 1 || 31 < day) {
      if (day != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(DAY, day);
      }
    }
    this.day = day;
  }

  /**
   * <p>Set the number of minutes in the timezone offset.</p>
   *
   * <p>Unset this field by invoking the setter with a parameter value of {@link
   * DatatypeConstants#FIELD_UNDEFINED}.</p>
   *
   * @param offset value constraints summarized in <a href="#datetimefield-timezone"> timezone field
   * of date/time field mapping table</a>.
   * @throws IllegalArgumentException if <code>offset</code> parameter is outside value constraints
   * for the field as specified in <a href="#datetimefieldmapping">date/time field mapping
   * table</a>.
   */
  public void setTimezone(int offset) {
    if (offset < -14 * 60 || 14 * 60 < offset) {
      if (offset != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(TIMEZONE, offset);
      }
    }
    this.timezone = offset;
  }

  /**
   * <p>Set time as one unit.</p>
   *
   * @param hour value constraints are summarized in <a href="#datetimefield-hour">hour field of
   * date/time field mapping table</a>.
   * @param minute value constraints are summarized in <a href="#datetimefield-minute">minute field
   * of date/time field mapping table</a>.
   * @param second value constraints are summarized in <a href="#datetimefield-second">second field
   * of date/time field mapping table</a>.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   * @see #setTime(int, int, int, BigDecimal)
   */
  public void setTime(int hour, int minute, int second) {
    setTime(hour, minute, second, null);
  }

  private void invalidFieldValue(int field, int value) {
    throw new IllegalArgumentException(
        DatatypeMessageFormatter.formatMessage(null, "InvalidFieldValue",
            new Object[]{new Integer(value), FIELD_NAME[field]})
    );
  }

  private void testHour() {

    // http://www.w3.org/2001/05/xmlschema-errata#e2-45
    if (getHour() == 24) {
      if (getMinute() != 0
          || getSecond() != 0) {
        invalidFieldValue(HOUR, getHour());
      }
      // while 0-24 is acceptable in the lexical space, 24 is not valid in value space
      // W3C XML Schema Part 2, Section 3.2.7.1
      setHour(0, false);
      add(new DurationImpl(true, 0, 0, 1, 0, 0, 0));
    }
  }

  public void setHour(int hour) {

    setHour(hour, true);
  }

  private void setHour(int hour, boolean validate) {

    if (hour < 0 || hour > 24) {
      if (hour != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(HOUR, hour);
      }
    }

    this.hour = hour;

    if (validate) {
      testHour();
    }
  }

  public void setMinute(int minute) {
    if (minute < 0 || 59 < minute) {
      if (minute != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(MINUTE, minute);
      }
    }
    this.minute = minute;
  }

  public void setSecond(int second) {
    if (second < 0 || 60 < second)   // leap second allows for 60
    {
      if (second != DatatypeConstants.FIELD_UNDEFINED) {
        invalidFieldValue(SECOND, second);
      }
    }
    this.second = second;
  }

  /**
   * <p>Set time as one unit, including the optional infinite precison
   * fractional seconds.</p>
   *
   * @param hour value constraints are summarized in <a href="#datetimefield-hour">hour field of
   * date/time field mapping table</a>.
   * @param minute value constraints are summarized in <a href="#datetimefield-minute">minute field
   * of date/time field mapping table</a>.
   * @param second value constraints are summarized in <a href="#datetimefield-second">second field
   * of date/time field mapping table</a>.
   * @param fractional value of <code>null</code> indicates this optional field is not set.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   */
  public void setTime(
      int hour,
      int minute,
      int second,
      BigDecimal fractional) {

    setHour(hour, false);

    setMinute(minute);
    if (second != 60) {
      setSecond(second);
    } else if ((hour == 23 && minute == 59) || (hour == 0 && minute == 0)) {
      setSecond(second);
    } else {
      invalidFieldValue(SECOND, second);
    }

    setFractionalSecond(fractional);

    // must test hour after setting seconds
    testHour();
  }


  /**
   * <p>Set time as one unit, including optional milliseconds.</p>
   *
   * @param hour value constraints are summarized in <a href="#datetimefield-hour">hour field of
   * date/time field mapping table</a>.
   * @param minute value constraints are summarized in <a href="#datetimefield-minute">minute field
   * of date/time field mapping table</a>.
   * @param second value constraints are summarized in <a href="#datetimefield-second">second field
   * of date/time field mapping table</a>.
   * @param millisecond value of {@link DatatypeConstants#FIELD_UNDEFINED} indicates this optional
   * field is not set.
   * @throws IllegalArgumentException if any parameter is outside value constraints for the field as
   * specified in <a href="#datetimefieldmapping">date/time field mapping table</a>.
   */
  public void setTime(int hour, int minute, int second, int millisecond) {

    setHour(hour, false);

    setMinute(minute);
    if (second != 60) {
      setSecond(second);
    } else if ((hour == 23 && minute == 59) || (hour == 0 && minute == 0)) {
      setSecond(second);
    } else {
      invalidFieldValue(SECOND, second);
    }
    setMillisecond(millisecond);

    // must test hour after setting seconds
    testHour();
  }

  // comparisons

  /**
   * <p>Compare two instances of W3C XML Schema 1.0 date/time datatypes according to partial order
   * relation defined in <a href="http://www.w3.org/TR/xmlschema-2/#dateTime-order">W3C XML Schema
   * 1.0 Part 2, Section 3.2.7.3, <i>Order relation on dateTime</i></a>.</p>
   *
   * <p><code>xsd:dateTime</code> datatype field mapping to accessors of this class are defined in
   * <a href="#datetimefieldmapping">date/time field mapping table</a>.</p>
   *
   * @param rhs instance of <code>XMLGregorianCalendar</code> to compare
   * @return the relationship between <code>lhs</code> and <code>rhs</code> as {@link
   * DatatypeConstants#LESSER}, {@link DatatypeConstants#EQUAL}, {@link DatatypeConstants#GREATER}
   * or {@link DatatypeConstants#INDETERMINATE}.
   * @throws NullPointerException if <code>lhs</code> or <code>rhs</code> parameters are null.
   */
  public int compare(XMLGregorianCalendar rhs) {

    XMLGregorianCalendar lhs = this;

    int result = DatatypeConstants.INDETERMINATE;
    XMLGregorianCalendarImpl P = (XMLGregorianCalendarImpl) lhs;
    XMLGregorianCalendarImpl Q = (XMLGregorianCalendarImpl) rhs;

    if (P.getTimezone() == Q.getTimezone()) {
      // Optimization:
      // both instances are in same timezone or
      // both are FIELD_UNDEFINED.
      // Avoid costly normalization of timezone to 'Z' time.
      return internalCompare(P, Q);

    } else if (P.getTimezone() != DatatypeConstants.FIELD_UNDEFINED &&
        Q.getTimezone() != DatatypeConstants.FIELD_UNDEFINED) {

      // Both instances have different timezones.
      // Normalize to UTC time and compare.
      P = (XMLGregorianCalendarImpl) P.normalize();
      Q = (XMLGregorianCalendarImpl) Q.normalize();
      return internalCompare(P, Q);
    } else if (P.getTimezone() != DatatypeConstants.FIELD_UNDEFINED) {

      if (P.getTimezone() != 0) {
        P = (XMLGregorianCalendarImpl) P.normalize();
      }

      // C. step 1
      XMLGregorianCalendar MinQ = Q.normalizeToTimezone(DatatypeConstants.MIN_TIMEZONE_OFFSET);
      result = internalCompare(P, MinQ);
      if (result == DatatypeConstants.LESSER) {
        return result;
      }

      // C. step 2
      XMLGregorianCalendar MaxQ = Q.normalizeToTimezone(DatatypeConstants.MAX_TIMEZONE_OFFSET);
      result = internalCompare(P, MaxQ);
      if (result == DatatypeConstants.GREATER) {
        return result;
      } else {
        // C. step 3
        return DatatypeConstants.INDETERMINATE;
      }
    } else { // Q.getTimezone() != DatatypeConstants.FIELD_UNDEFINED
      // P has no timezone and Q does.
      if (Q.getTimezone() != 0) {
        Q = (XMLGregorianCalendarImpl) Q.normalizeToTimezone(Q.getTimezone());
      }

      // D. step 1
      XMLGregorianCalendar MaxP = P.normalizeToTimezone(DatatypeConstants.MAX_TIMEZONE_OFFSET);
      result = internalCompare(MaxP, Q);
      if (result == DatatypeConstants.LESSER) {
        return result;
      }

      // D. step 2
      XMLGregorianCalendar MinP = P.normalizeToTimezone(DatatypeConstants.MIN_TIMEZONE_OFFSET);
      result = internalCompare(MinP, Q);
      if (result == DatatypeConstants.GREATER) {
        return result;
      } else {
        // D. step 3
        return DatatypeConstants.INDETERMINATE;
      }
    }
  }

  /**
   * <p>Normalize this instance to UTC.</p>
   *
   * <p>2000-03-04T23:00:00+03:00 normalizes to 2000-03-04T20:00:00Z</p>
   * <p>Implements W3C XML Schema Part 2, Section 3.2.7.3 (A).</p>
   */
  public XMLGregorianCalendar normalize() {

    XMLGregorianCalendar normalized = normalizeToTimezone(timezone);

    // if timezone was undefined, leave it undefined
    if (getTimezone() == DatatypeConstants.FIELD_UNDEFINED) {
      normalized.setTimezone(DatatypeConstants.FIELD_UNDEFINED);
    }

    // if milliseconds was undefined, leave it undefined
    if (getMillisecond() == DatatypeConstants.FIELD_UNDEFINED) {
      normalized.setMillisecond(DatatypeConstants.FIELD_UNDEFINED);
    }

    return normalized;
  }

  /**
   * <p>Normalize this instance to UTC.</p>
   *
   * <p>2000-03-04T23:00:00+03:00 normalizes to 2000-03-04T20:00:00Z</p>
   * <p>Implements W3C XML Schema Part 2, Section 3.2.7.3 (A).</p>
   */
  private XMLGregorianCalendar normalizeToTimezone(int timezone) {

    int minutes = timezone;
    XMLGregorianCalendar result = (XMLGregorianCalendar) this.clone();

    // normalizing to UTC time negates the timezone offset before
    // addition.
    minutes = -minutes;
    Duration d = new DurationImpl(minutes >= 0, // isPositive
        0, //years
        0, //months
        0, //days
        0, //hours
        minutes < 0 ? -minutes : minutes, // absolute
        0  //seconds
    );
    result.add(d);

    // set to zulu UTC time.
    result.setTimezone(0);
    return result;
  }

  /**
   * <p>Implements Step B from http://www.w3.org/TR/xmlschema-2/#dateTime-order </p>
   *
   * @param P calendar instance with normalized timezone offset or having same timezone as Q
   * @param Q calendar instance with normalized timezone offset or having same timezone as P
   * @return result of comparing P and Q, value of {@link DatatypeConstants#EQUAL}, {@link
   * DatatypeConstants#LESSER}, {@link DatatypeConstants#GREATER} or {@link
   * DatatypeConstants#INDETERMINATE}.
   */
  private static int internalCompare(XMLGregorianCalendar P,
      XMLGregorianCalendar Q) {

    int result;

    // compare Year.
    if (P.getEon() == Q.getEon()) {

      // Eon field is only equal when null.
      // optimized case for comparing year not requiring eon field.
      result = compareField(P.getYear(), Q.getYear());
      if (result != DatatypeConstants.EQUAL) {
        return result;
      }
    } else {
      result = compareField(P.getEonAndYear(), Q.getEonAndYear());
      if (result != DatatypeConstants.EQUAL) {
        return result;
      }
    }

    result = compareField(P.getMonth(), Q.getMonth());
    if (result != DatatypeConstants.EQUAL) {
      return result;
    }

    result = compareField(P.getDay(), Q.getDay());
    if (result != DatatypeConstants.EQUAL) {
      return result;
    }

    result = compareField(P.getHour(), Q.getHour());
    if (result != DatatypeConstants.EQUAL) {
      return result;
    }

    result = compareField(P.getMinute(), Q.getMinute());
    if (result != DatatypeConstants.EQUAL) {
      return result;
    }
    result = compareField(P.getSecond(), Q.getSecond());
    if (result != DatatypeConstants.EQUAL) {
      return result;
    }

    result = compareField(P.getFractionalSecond(), Q.getFractionalSecond());
    return result;
  }

  /**
   * <p>Implement Step B from
   * http://www.w3.org/TR/xmlschema-2/#dateTime-order.</p>
   */
  private static int compareField(int Pfield, int Qfield) {
    if (Pfield == Qfield) {

      //fields are either equal in value or both undefined.
      // Step B. 1.1 AND optimized result of performing 1.1-1.4.
      return DatatypeConstants.EQUAL;
    } else {
      if (Pfield == DatatypeConstants.FIELD_UNDEFINED
          || Qfield == DatatypeConstants.FIELD_UNDEFINED) {
        // Step B. 1.2
        return DatatypeConstants.INDETERMINATE;
      } else {
        // Step B. 1.3-4.
        return (Pfield < Qfield ? DatatypeConstants.LESSER : DatatypeConstants.GREATER);
      }
    }
  }

  private static int compareField(BigInteger Pfield, BigInteger Qfield) {
    if (Pfield == null) {
      return (Qfield == null ? DatatypeConstants.EQUAL : DatatypeConstants.INDETERMINATE);
    }
    if (Qfield == null) {
      return DatatypeConstants.INDETERMINATE;
    }
    return Pfield.compareTo(Qfield);
  }

  private static int compareField(BigDecimal Pfield, BigDecimal Qfield) {
    // optimization. especially when both arguments are null.
    if (Pfield == Qfield) {
      return DatatypeConstants.EQUAL;
    }

    if (Pfield == null) {
      Pfield = DECIMAL_ZERO;
    }

    if (Qfield == null) {
      Qfield = DECIMAL_ZERO;
    }

    return Pfield.compareTo(Qfield);
  }

  /**
   * <p>Indicates whether parameter <code>obj</code> is "equal to" this one.</p>
   *
   * @param obj to compare.
   * @return <code>true</code> when <code>compare(this,(XMLGregorianCalendar)obj) == EQUAL.</code>.
   */
  public boolean equals(Object obj) {

    if (obj == null || !(obj instanceof XMLGregorianCalendar)) {
      return false;
    }
    return compare((XMLGregorianCalendar) obj) == DatatypeConstants.EQUAL;
  }

  /**
   * <p>Returns a hash code consistent with the definition of the equals method.</p>
   *
   * @return hash code of this object.
   */
  public int hashCode() {

    // Following two dates compare to EQUALS since in different timezones.
    // 2000-01-15T12:00:00-05:00 == 2000-01-15T13:00:00-04:00
    //
    // Must ensure both instances generate same hashcode by normalizing
    // this to UTC timezone.
    int timezone = getTimezone();
    if (timezone == DatatypeConstants.FIELD_UNDEFINED) {
      timezone = 0;
    }
    XMLGregorianCalendar gc = this;
    if (timezone != 0) {
      gc = this.normalizeToTimezone(getTimezone());
    }
    return gc.getYear() + gc.getMonth() + gc.getDay() +
        gc.getHour() + gc.getMinute() + gc.getSecond();
  }


  /**
   * <p>Constructs a new XMLGregorianCalendar object by parsing its lexical string representation as
   * defined in <a href="http://www.w3.org/TR/xmlschema-2/#dateTime-order">XML Schema 1.0 Part 2,
   * Section 3.2.[7-14].1, <i>Lexical Representation</i>.</a></p>
   *
   * <p>The string representation may not have any leading and trailing whitespaces.</p>
   *
   * <p>The parsing is done field by field so that the following holds for any lexically correct
   * string x:</p>
   * <pre>
   * new XMLGregorianCalendar(x).toXMLFormat().equals(x)
   * </pre>
   * Except for the noted lexical/canonical representation mismatches listed in <a
   * href="http://www.w3.org/2001/05/xmlschema-errata#e2-45"> XML Schema 1.0 errata, Section
   * 3.2.7.2</a>.
   *
   * <p>Returns a non-null valid XMLGregorianCalendar object that holds the value indicated by the
   * lexicalRepresentation parameter.</p>
   *
   * @param lexicalRepresentation Lexical representation of one the 8 XML Schema calendar
   * datatypes.
   * @return <code>XMLGregorianCalendar</code> created from parsing <code>lexicalRepresentation</code>
   * parameter.
   * @throws IllegalArgumentException If the given string does not conform to the aforementioned
   * specification.
   * @throws NullPointerException If the given string is null.
   */
  public static XMLGregorianCalendar parse(String lexicalRepresentation) {

    return new XMLGregorianCalendarImpl(lexicalRepresentation);
  }

  /**
   * <p>Return the lexical representation of <code>this</code> instance. The format is specified in
   * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime-order">XML Schema 1.0 Part 2, Section
   * 3.2.[7-14].1, <i>Lexical Representation</i>".</a></p>
   *
   * <p>Specific target lexical representation format is determined by {@link
   * #getXMLSchemaType()}.</p>
   *
   * @return XML, as <code>String</code>, representation of this <code>XMLGregorianCalendar</code>
   * @throws java.lang.IllegalStateException if the combination of set fields does not match one of
   * the eight defined XML Schema builtin date/time datatypes.
   */
  public String toXMLFormat() {

    QName typekind = getXMLSchemaType();

    String formatString = null;
    // Fix 4971612: invalid SCCS macro substitution in data string
    //   no %{alpha}% to avoid SCCS macro substitution
    if (typekind == DatatypeConstants.DATETIME) {
      formatString = "%Y-%M-%DT%h:%m:%s" + "%z";
    } else if (typekind == DatatypeConstants.DATE) {
      formatString = "%Y-%M-%D" + "%z";
    } else if (typekind == DatatypeConstants.TIME) {
      formatString = "%h:%m:%s" + "%z";
    } else if (typekind == DatatypeConstants.GMONTH) {
      formatString = "--%M" + "%z";
    } else if (typekind == DatatypeConstants.GDAY) {
      formatString = "---%D" + "%z";
    } else if (typekind == DatatypeConstants.GYEAR) {
      formatString = "%Y" + "%z";
    } else if (typekind == DatatypeConstants.GYEARMONTH) {
      formatString = "%Y-%M" + "%z";
    } else if (typekind == DatatypeConstants.GMONTHDAY) {
      formatString = "--%M-%D" + "%z";
    }
    return format(formatString);
  }

  /**
   * <p>Return the name of the XML Schema date/time type that this instance
   * maps to. Type is computed based on fields that are set.</p>
   *
   * <table border="2" rules="all" cellpadding="2">
   * <thead>
   * <tr>
   * <th align="center" colspan="7">
   * Required fields for XML Schema 1.0 Date/Time Datatypes.<br/>
   * <i>(timezone is optional for all date/time datatypes)</i>
   * </th>
   * </tr>
   * </thead>
   * <tbody>
   * <tr>
   * <td>Datatype</td>
   * <td>year</td>
   * <td>month</td>
   * <td>day</td>
   * <td>hour</td>
   * <td>minute</td>
   * <td>second</td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#DATETIME}</td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#DATE}</td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#TIME}</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * <td>X</td>
   * <td>X</td>
   * <td>X</td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#GYEARMONTH}</td>
   * <td>X</td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#GMONTHDAY}</td>
   * <td></td>
   * <td>X</td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#GYEAR}</td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#GMONTH}</td>
   * <td></td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * <tr>
   * <td>{@link DatatypeConstants#GDAY}</td>
   * <td></td>
   * <td></td>
   * <td>X</td>
   * <td></td>
   * <td></td>
   * <td></td>
   * </tr>
   * </tbody>
   * </table>
   *
   * @return One of the following class constants: {@link DatatypeConstants#DATETIME}, {@link
   * DatatypeConstants#TIME}, {@link DatatypeConstants#DATE}, {@link DatatypeConstants#GYEARMONTH},
   * {@link DatatypeConstants#GMONTHDAY}, {@link DatatypeConstants#GYEAR}, {@link
   * DatatypeConstants#GMONTH} or {@link DatatypeConstants#GDAY}.
   * @throws java.lang.IllegalStateException if the combination of set fields does not match one of
   * the eight defined XML Schema builtin date/time datatypes.
   */
  public QName getXMLSchemaType() {

    int mask =
        (year != DatatypeConstants.FIELD_UNDEFINED ? 0x20 : 0) |
            (month != DatatypeConstants.FIELD_UNDEFINED ? 0x10 : 0) |
            (day != DatatypeConstants.FIELD_UNDEFINED ? 0x08 : 0) |
            (hour != DatatypeConstants.FIELD_UNDEFINED ? 0x04 : 0) |
            (minute != DatatypeConstants.FIELD_UNDEFINED ? 0x02 : 0) |
            (second != DatatypeConstants.FIELD_UNDEFINED ? 0x01 : 0);

    switch (mask) {
      case 0x3F:
        return DatatypeConstants.DATETIME;
      case 0x38:
        return DatatypeConstants.DATE;
      case 0x07:
        return DatatypeConstants.TIME;
      case 0x30:
        return DatatypeConstants.GYEARMONTH;
      case 0x18:
        return DatatypeConstants.GMONTHDAY;
      case 0x20:
        return DatatypeConstants.GYEAR;
      case 0x10:
        return DatatypeConstants.GMONTH;
      case 0x08:
        return DatatypeConstants.GDAY;
      default:
        throw new IllegalStateException(
            this.getClass().getName()
                + "#getXMLSchemaType() :"
                + DatatypeMessageFormatter.formatMessage(null, "InvalidXGCFields", null)
        );
    }
  }


  /**
   * Validate instance by <code>getXMLSchemaType()</code> constraints.
   *
   * @return true if data values are valid.
   */
  public boolean isValid() {
    // since setters do not allow for invalid values,
    // (except for exceptional case of year field of zero),
    // no need to check for anything except for constraints
    // between fields.

    //check if days in month is valid. Can be dependent on leap year.
    if (getMonth() == DatatypeConstants.FEBRUARY) {
      // years could not be set
      int maxDays = 29;

      if (eon == null) {
        if (year != DatatypeConstants.FIELD_UNDEFINED) {
          maxDays = maximumDayInMonthFor(year, getMonth());
        }
      } else {
        BigInteger years = getEonAndYear();
        if (years != null) {
          maxDays = maximumDayInMonthFor(getEonAndYear(), DatatypeConstants.FEBRUARY);
        }
      }
      if (getDay() > maxDays) {
        return false;
      }
    }

    // http://www.w3.org/2001/05/xmlschema-errata#e2-45
    if (getHour() == 24) {
      if (getMinute() != 0) {
        return false;
      } else if (getSecond() != 0) {
        return false;
      }
    }

    // XML Schema 1.0 specification defines year value of zero as
    // invalid. Allow this class to set year field to zero
    // since XML Schema 1.0 errata states that lexical zero will
    // be allowed in next version and treated as 1 B.C.E.
    if (eon == null) {
      // optimize check.
      if (year == 0) {
        return false;
      }
    } else {
      BigInteger yearField = getEonAndYear();
      if (yearField != null) {
        int result = compareField(yearField, BigInteger.ZERO);
        if (result == DatatypeConstants.EQUAL) {
          return false;
        }
      }
    }
    return true;
  }

  /**
   * <p>Add <code>duration</code> to this instance.<\p>
   *
   * <p>The computation is specified in <a href="http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes">XML
   * Schema 1.0 Part 2, Appendix E, <i>Adding durations to dateTimes</i>></a>. <a
   * href="#datetimefieldsmapping">date/time field mapping table</a> defines the mapping from XML
   * Schema 1.0 <code>dateTime</code> fields to this class' representation of those fields.</p>
   *
   * @param duration Duration to add to this <code>XMLGregorianCalendar</code>.
   * @throws NullPointerException when <code>duration</code> parameter is <code>null</code>.
   */
  public void add(Duration duration) {

        /*
           * Extracted from
           * http://www.w3.org/TR/xmlschema-2/#adding-durations-to-dateTimes
           * to ensure implemented properly. See spec for definitions of methods
           * used in algorithm.
           *
           * Given a dateTime S and a duration D, specifies how to compute a
           * dateTime E where E is the end of the time period with start S and
           * duration D i.e. E = S + D.
           *
           * The following is the precise specification.
           * These steps must be followed in the same order.
           * If a field in D is not specified, it is treated as if it were zero.
           * If a field in S is not specified, it is treated in the calculation
           * as if it were the minimum allowed value in that field, however,
           * after the calculation is concluded, the corresponding field in
           * E is removed (set to unspecified).
           *
           * Months (may be modified additionally below)
               *  temp := S[month] + D[month]
               *  E[month] := modulo(temp, 1, 13)
               *  carry := fQuotient(temp, 1, 13)
           */

    boolean fieldUndefined[] = {
        false,
        false,
        false,
        false,
        false,
        false
    };

    int signum = duration.getSign();

    int startMonth = getMonth();
    if (startMonth == DatatypeConstants.FIELD_UNDEFINED) {
      startMonth = DatatypeConstants.JANUARY;
      fieldUndefined[MONTH] = true;
    }

    BigInteger dMonths = sanitize(duration.getField(DatatypeConstants.MONTHS), signum);
    BigInteger temp = BigInteger.valueOf((long) startMonth).add(dMonths);
    setMonth(temp.subtract(BigInteger.ONE).mod(TWELVE).intValue() + 1);
    BigInteger carry =
        new BigDecimal(temp.subtract(BigInteger.ONE))
            .divide(new BigDecimal(TWELVE), BigDecimal.ROUND_FLOOR).toBigInteger();

        /* Years (may be modified additionally below)
            *  E[year] := S[year] + D[year] + carry
            */
    BigInteger startYear = getEonAndYear();
    if (startYear == null) {
      fieldUndefined[YEAR] = true;
      startYear = BigInteger.ZERO;
    }
    BigInteger dYears = sanitize(duration.getField(DatatypeConstants.YEARS), signum);
    BigInteger endYear = startYear.add(dYears).add(carry);
    setYear(endYear);

        /* Zone
               *  E[zone] := S[zone]
           *
           * no-op since adding to this, not to a new end point.
           */

        /* Seconds
            *  temp := S[second] + D[second]
            *  E[second] := modulo(temp, 60)
            *  carry := fQuotient(temp, 60)
            */
    BigDecimal startSeconds;
    if (getSecond() == DatatypeConstants.FIELD_UNDEFINED) {
      fieldUndefined[SECOND] = true;
      startSeconds = DECIMAL_ZERO;
    } else {
      // seconds + fractionalSeconds
      startSeconds = getSeconds();
    }

    // Duration seconds is SECONDS + FRACTIONALSECONDS.
    BigDecimal dSeconds = DurationImpl
        .sanitize((BigDecimal) duration.getField(DatatypeConstants.SECONDS), signum);
    BigDecimal tempBD = startSeconds.add(dSeconds);
    BigDecimal fQuotient =
        new BigDecimal(
            new BigDecimal(tempBD.toBigInteger()).divide(DECIMAL_SIXTY, BigDecimal.ROUND_FLOOR)
                .toBigInteger());
    BigDecimal endSeconds = tempBD.subtract(fQuotient.multiply(DECIMAL_SIXTY));

    carry = fQuotient.toBigInteger();
    setSecond(endSeconds.intValue());
    BigDecimal tempFracSeconds = endSeconds
        .subtract(new BigDecimal(BigInteger.valueOf((long) getSecond())));
    if (tempFracSeconds.compareTo(DECIMAL_ZERO) < 0) {
      setFractionalSecond(DECIMAL_ONE.add(tempFracSeconds));
      if (getSecond() == 0) {
        setSecond(59);
        carry = carry.subtract(BigInteger.ONE);
      } else {
        setSecond(getSecond() - 1);
      }
    } else {
      setFractionalSecond(tempFracSeconds);
    }

        /* Minutes
               *  temp := S[minute] + D[minute] + carry
               *  E[minute] := modulo(temp, 60)
               *  carry := fQuotient(temp, 60)
           */
    int startMinutes = getMinute();
    if (startMinutes == DatatypeConstants.FIELD_UNDEFINED) {
      fieldUndefined[MINUTE] = true;
      startMinutes = 0;
    }
    BigInteger dMinutes = sanitize(duration.getField(DatatypeConstants.MINUTES), signum);

    temp = BigInteger.valueOf(startMinutes).add(dMinutes).add(carry);
    setMinute(temp.mod(SIXTY).intValue());
    carry = new BigDecimal(temp).divide(DECIMAL_SIXTY, BigDecimal.ROUND_FLOOR).toBigInteger();

        /* Hours
               *  temp := S[hour] + D[hour] + carry
               *  E[hour] := modulo(temp, 24)
               *  carry := fQuotient(temp, 24)
           */
    int startHours = getHour();
    if (startHours == DatatypeConstants.FIELD_UNDEFINED) {
      fieldUndefined[HOUR] = true;
      startHours = 0;
    }
    BigInteger dHours = sanitize(duration.getField(DatatypeConstants.HOURS), signum);

    temp = BigInteger.valueOf(startHours).add(dHours).add(carry);
    setHour(temp.mod(TWENTY_FOUR).intValue(), false);
    carry = new BigDecimal(temp).divide(new BigDecimal(TWENTY_FOUR), BigDecimal.ROUND_FLOOR)
        .toBigInteger();

        /* Days
           *  if S[day] > maximumDayInMonthFor(E[year], E[month])
           *       + tempDays := maximumDayInMonthFor(E[year], E[month])
           *  else if S[day] < 1
           *       + tempDays := 1
           *  else
           *       + tempDays := S[day]
           *  E[day] := tempDays + D[day] + carry
           *  START LOOP
           *       + IF E[day] < 1
           *             # E[day] := E[day] +
            *                 maximumDayInMonthFor(E[year], E[month] - 1)
           *             # carry := -1
           *       + ELSE IF E[day] > maximumDayInMonthFor(E[year], E[month])
           *             # E[day] :=
            *                    E[day] - maximumDayInMonthFor(E[year], E[month])
           *             # carry := 1
           *       + ELSE EXIT LOOP
           *       + temp := E[month] + carry
           *       + E[month] := modulo(temp, 1, 13)
           *       + E[year] := E[year] + fQuotient(temp, 1, 13)
           *       + GOTO START LOOP
           */
    BigInteger tempDays;
    int startDay = getDay();
    if (startDay == DatatypeConstants.FIELD_UNDEFINED) {
      fieldUndefined[DAY] = true;
      startDay = 1;
    }
    BigInteger dDays = sanitize(duration.getField(DatatypeConstants.DAYS), signum);
    int maxDayInMonth = maximumDayInMonthFor(getEonAndYear(), getMonth());
    if (startDay > maxDayInMonth) {
      tempDays = BigInteger.valueOf(maxDayInMonth);
    } else if (startDay < 1) {
      tempDays = BigInteger.ONE;
    } else {
      tempDays = BigInteger.valueOf(startDay);
    }
    BigInteger endDays = tempDays.add(dDays).add(carry);
    int monthCarry;
    int intTemp;
    while (true) {
      if (endDays.compareTo(BigInteger.ONE) < 0) {
        // calculate days in previous month, watch for month roll over
        BigInteger mdimf = null;
        if (month >= 2) {
          mdimf = BigInteger.valueOf(maximumDayInMonthFor(getEonAndYear(), getMonth() - 1));
        } else {
          // roll over to December of previous year
          mdimf = BigInteger.valueOf(
              maximumDayInMonthFor(getEonAndYear().subtract(BigInteger.valueOf((long) 1)), 12));
        }
        endDays = endDays.add(mdimf);
        monthCarry = -1;
      } else if (
          endDays.compareTo(BigInteger.valueOf(maximumDayInMonthFor(getEonAndYear(), getMonth())))
              > 0) {
        endDays = endDays
            .add(BigInteger.valueOf(-maximumDayInMonthFor(getEonAndYear(), getMonth())));
        monthCarry = 1;
      } else {
        break;
      }

      intTemp = getMonth() + monthCarry;
      int endMonth = (intTemp - 1) % (13 - 1);
      int quotient;
      if (endMonth < 0) {
        endMonth = (13 - 1) + endMonth + 1;
        quotient = new BigDecimal(intTemp - 1).divide(new BigDecimal(TWELVE), BigDecimal.ROUND_UP)
            .intValue();
      } else {
        quotient = (intTemp - 1) / (13 - 1);
        endMonth += 1;
      }
      setMonth(endMonth);
      if (quotient != 0) {
        setYear(getEonAndYear().add(BigInteger.valueOf(quotient)));
      }
    }
    setDay(endDays.intValue());

    // set fields that where undefined before this addition, back to undefined.
    for (int i = YEAR; i <= SECOND; i++) {
      if (fieldUndefined[i]) {
        switch (i) {
          case YEAR:
            setYear(DatatypeConstants.FIELD_UNDEFINED);
            break;
          case MONTH:
            setMonth(DatatypeConstants.FIELD_UNDEFINED);
            break;
          case DAY:
            setDay(DatatypeConstants.FIELD_UNDEFINED);
            break;
          case HOUR:
            setHour(DatatypeConstants.FIELD_UNDEFINED, false);
            break;
          case MINUTE:
            setMinute(DatatypeConstants.FIELD_UNDEFINED);
            break;
          case SECOND:
            setSecond(DatatypeConstants.FIELD_UNDEFINED);
            setFractionalSecond(null);
            break;
        }
      }
    }
  }

  private static final BigInteger FOUR = BigInteger.valueOf(4);
  private static final BigInteger HUNDRED = BigInteger.valueOf(100);
  private static final BigInteger FOUR_HUNDRED = BigInteger.valueOf(400);
  private static final BigInteger SIXTY = BigInteger.valueOf(60);
  private static final BigInteger TWENTY_FOUR = BigInteger.valueOf(24);
  private static final BigInteger TWELVE = BigInteger.valueOf(12);
  private static final BigDecimal DECIMAL_ZERO = new BigDecimal("0");
  private static final BigDecimal DECIMAL_ONE = new BigDecimal("1");
  private static final BigDecimal DECIMAL_SIXTY = new BigDecimal("60");


  private static int daysInMonth[] = {0,  // XML Schema months start at 1.
      31, 28, 31, 30, 31, 30,
      31, 31, 30, 31, 30, 31};

  private static int maximumDayInMonthFor(BigInteger year, int month) {
    if (month != DatatypeConstants.FEBRUARY) {
      return daysInMonth[month];
    } else {
      if (year.mod(FOUR_HUNDRED).equals(BigInteger.ZERO) ||
          (!year.mod(HUNDRED).equals(BigInteger.ZERO) &&
              year.mod(FOUR).equals(BigInteger.ZERO))) {
        // is a leap year.
        return 29;
      } else {
        return daysInMonth[month];
      }
    }
  }

  private static int maximumDayInMonthFor(int year, int month) {
    if (month != DatatypeConstants.FEBRUARY) {
      return daysInMonth[month];
    } else {
      if (((year % 400) == 0) ||
          (((year % 100) != 0) && ((year % 4) == 0))) {
        // is a leap year.
        return 29;
      } else {
        return daysInMonth[DatatypeConstants.FEBRUARY];
      }
    }
  }

  /**
   * <p>Convert <code>this</code> to <code>java.util.GregorianCalendar</code>.</p>
   *
   * <p>When <code>this</code> instance has an undefined field, this conversion relies on the
   * <code>java.util.GregorianCalendar</code> default for its corresponding field. A notable
   * difference between XML Schema 1.0 date/time datatypes and <code>java.util.GregorianCalendar</code>
   * is that Timezone value is optional for date/time datatypes and it is a required field for
   * <code>java.util.GregorianCalendar</code>. See javadoc for <code>java.util.TimeZone.getDefault()</code>
   * on how the default is determined. To explicitly specify the <code>TimeZone</code> instance, see
   * {@link #toGregorianCalendar(TimeZone, Locale, XMLGregorianCalendar)}.</p>
   *
   * <table border="2" rules="all" cellpadding="2"> <thead> <tr> <th align="center" colspan="2">
   * Field by Field Conversion from this class to <code>java.util.GregorianCalendar</code> </th>
   * </tr> </thead> <tbody> <tr> <th><code>java.util.GregorianCalendar</code> field</th>
   * <th><code>javax.xml.datatype.XMLGregorianCalendar</code> field</th> </tr> <tr>
   * <th><code>ERA</code></th> <th>{@link #getEonAndYear()}<code>.signum() < 0 ?
   * GregorianCalendar.BC : GregorianCalendar.AD</code></th> </tr> <tr> <th><code>YEAR</code></th>
   * <th>{@link #getEonAndYear()}<code>.abs().intValue()</code><i>*</i></th> </tr> <tr>
   * <th><code>MONTH</code></th> <th>{@link #getMonth()}<code> - 1</code></th> </tr> <tr>
   * <th><code>DAY_OF_MONTH</code></th> <th>{@link #getDay()}</th> </tr> <tr>
   * <th><code>AM_PM</code></th> <th>{@link #getHour()} < 12 : Calendar.AM : Calendar.PM</th> </tr>
   * <tr> <th><code>HOUR_OF_DAY</code></th> <th>{@link #getHour()}</th> </tr> <tr>
   * <th><code>MINUTE</code></th> <th>{@link #getMinute()}</th> </tr> <tr>
   * <th><code>SECOND</code></th> <th>{@link #getSecond()}</th> </tr> <tr>
   * <th><code>MILLISECOND</code></th> <th>get millisecond order from {@link
   * #getFractionalSecond()}<i>*</i> </th> </tr> <tr> <th><code>GregorianCalendar.setTimeZone(TimeZone)</code></th>
   * <th>{@link #getTimezone()} formatted into Custom timezone id</th> </tr> </tbody> </table>
   * <i>*</i> designates possible loss of precision during the conversion due to source datatype
   * having higer precison than target datatype.
   *
   * <p>To ensure consistency in conversion implementations, the new <code>GregorianCalendar</code>
   * should be instantiated in following manner. <ul> <li>Using <code>timeZone</code> value as
   * defined above, create a new <code>java.util.GregorianCalendar(timeZone,Locale.getDefault())</code>.
   * </li> <li>Initialize all GregorianCalendar fields by calling {(@link
   * GegorianCalendar#clear()}.</li> <li>Obtain a pure Gregorian Calendar by invoking
   * <code>GregorianCalendar.setGregorianChange( new Date(Long.MIN_VALUE))</code>.</li> <li>Its
   * fields ERA, YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY, MINUTE, SECOND and MILLISECOND are set
   * using the method <code>Calendar.set(int,int)</code></li> </ul> </p>
   *
   * @see #toGregorianCalendar(java.util.TimeZone, java.util.Locale, XMLGregorianCalendar)
   */
  public java.util.GregorianCalendar toGregorianCalendar() {

    GregorianCalendar result = null;
    final int DEFAULT_TIMEZONE_OFFSET = DatatypeConstants.FIELD_UNDEFINED;
    TimeZone tz = getTimeZone(DEFAULT_TIMEZONE_OFFSET);
    /** Use the following instead for JDK7 only:
     * Locale locale = Locale.getDefault(Locale.Category.FORMAT);
     */
    Locale locale = getDefaultLocale();

    result = new GregorianCalendar(tz, locale);
    result.clear();
    result.setGregorianChange(PURE_GREGORIAN_CHANGE);

    // if year( and eon) are undefined, leave default Calendar values
    BigInteger year = getEonAndYear();
    if (year != null) {
      result.set(Calendar.ERA, year.signum() == -1 ? GregorianCalendar.BC : GregorianCalendar.AD);
      result.set(Calendar.YEAR, year.abs().intValue());
    }

    // only set month if it is set
    if (month != DatatypeConstants.FIELD_UNDEFINED) {
      // Calendar.MONTH is zero based while XMLGregorianCalendar month field is not.
      result.set(Calendar.MONTH, month - 1);
    }

    // only set day if it is set
    if (day != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.DAY_OF_MONTH, day);
    }

    // only set hour if it is set
    if (hour != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.HOUR_OF_DAY, hour);
    }

    // only set minute if it is set
    if (minute != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.MINUTE, minute);
    }

    // only set second if it is set
    if (second != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.SECOND, second);
    }

    // only set millisend if it is set
    if (fractionalSecond != null) {
      result.set(Calendar.MILLISECOND, getMillisecond());
    }

    return result;
  }

  /**
   * @return default locale
   */
  private Locale getDefaultLocale() {

    String lang = SecuritySupport.getSystemProperty("user.language.format");
    String country = SecuritySupport.getSystemProperty("user.country.format");
    String variant = SecuritySupport.getSystemProperty("user.variant.format");
    Locale locale = null;
    if (lang != null) {
      if (country != null) {
        if (variant != null) {
          locale = new Locale(lang, country, variant);
        } else {
          locale = new Locale(lang, country);
        }
      } else {
        locale = new Locale(lang);
      }
    }
    if (locale == null) {
      locale = Locale.getDefault();
    }
    return locale;
  }

  /**
   * <p>Convert <code>this</code> along with provided parameters
   * to <code>java.util.GregorianCalendar</code> instance.</p>
   *
   * <p> Since XML Schema 1.0 date/time datetypes has no concept of
   * timezone ids or daylight savings timezone ids, this conversion operation
   * allows the user to explicitly specify one with
   * <code>timezone</code> parameter.</p>
   *
   * <p>To compute the return value's <code>TimeZone</code> field,
   * <ul>
   * <li>when parameter <code>timeZone</code> is non-null,
   * it is the timezone field.</li>
   * <li>else when <code>this.getTimezone() != DatatypeConstants.FIELD_UNDEFINED</code>,
   * create a <code>java.util.TimeZone</code> with a custom timezone id
   * using the <code>this.getTimezone()</code>.</li>
   * <li>else when <code>defaults.getTimezone() != DatatypeConstants.FIELD_UNDEFINED</code>,
   * create a <code>java.util.TimeZone</code> with a custom timezone id
   * using <code>defaults.getTimezone()</code>.</li>
   * <li>else use the <code>GregorianCalendar</code> default timezone value
   * for the host is definedas specified by
   * <code>java.util.TimeZone.getDefault()</code>.</li></p>
   *
   * <p>To ensure consistency in conversion implementations, the new
   * <code>GregorianCalendar</code> should be instantiated in following
   * manner.
   * <ul>
   * <li>Create a new <code>java.util.GregorianCalendar(TimeZone,
   * Locale)</code> with TimeZone set as specified above and the
   * <code>Locale</code> parameter.
   * </li>
   * <li>Initialize all GregorianCalendar fields by calling {(@link GegorianCalendar#clear()}.</li>
   * <li>Obtain a pure Gregorian Calendar by invoking
   * <code>GregorianCalendar.setGregorianChange(
   * new Date(Long.MIN_VALUE))</code>.</li>
   * <li>Its fields ERA, YEAR, MONTH, DAY_OF_MONTH, HOUR_OF_DAY,
   * MINUTE, SECOND and MILLISECOND are set using the method
   * <code>Calendar.set(int,int)</code></li>
   * </ul>
   *
   * @param timezone provide Timezone. <code>null</code> is a legal value.
   * @param aLocale provide explicit Locale. Use default GregorianCalendar locale if value is
   * <code>null</code>.
   * @param defaults provide default field values to use when corresponding field for this instance
   * is DatatypeConstants.FIELD_UNDEFINED or null. If <code>defaults</code>is <code>null</code> or a
   * field within the specified <code>defaults</code> is undefined, just use
   * <code>java.util.GregorianCalendar</code> defaults.
   * @return a java.util.GregorianCalendar conversion of this instance.
   * @see #LEAP_YEAR_DEFAULT
   */
  public GregorianCalendar toGregorianCalendar(TimeZone timezone,
      Locale aLocale,
      XMLGregorianCalendar defaults) {
    GregorianCalendar result = null;
    TimeZone tz = timezone;
    if (tz == null) {
      int defaultZoneoffset = DatatypeConstants.FIELD_UNDEFINED;
      if (defaults != null) {
        defaultZoneoffset = defaults.getTimezone();
      }
      tz = getTimeZone(defaultZoneoffset);
    }
    if (aLocale == null) {
      aLocale = Locale.getDefault();
    }
    result = new GregorianCalendar(tz, aLocale);
    result.clear();
    result.setGregorianChange(PURE_GREGORIAN_CHANGE);

    // if year( and eon) are undefined, leave default Calendar values
    BigInteger year = getEonAndYear();
    if (year != null) {
      result.set(Calendar.ERA, year.signum() == -1 ? GregorianCalendar.BC : GregorianCalendar.AD);
      result.set(Calendar.YEAR, year.abs().intValue());
    } else {
      // use default if set
      BigInteger defaultYear = (defaults != null) ? defaults.getEonAndYear() : null;
      if (defaultYear != null) {
        result.set(Calendar.ERA,
            defaultYear.signum() == -1 ? GregorianCalendar.BC : GregorianCalendar.AD);
        result.set(Calendar.YEAR, defaultYear.abs().intValue());
      }
    }

    // only set month if it is set
    if (month != DatatypeConstants.FIELD_UNDEFINED) {
      // Calendar.MONTH is zero based while XMLGregorianCalendar month field is not.
      result.set(Calendar.MONTH, month - 1);
    } else {
      // use default if set
      int defaultMonth =
          (defaults != null) ? defaults.getMonth() : DatatypeConstants.FIELD_UNDEFINED;
      if (defaultMonth != DatatypeConstants.FIELD_UNDEFINED) {
        // Calendar.MONTH is zero based while XMLGregorianCalendar month field is not.
        result.set(Calendar.MONTH, defaultMonth - 1);
      }
    }

    // only set day if it is set
    if (day != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.DAY_OF_MONTH, day);
    } else {
      // use default if set
      int defaultDay = (defaults != null) ? defaults.getDay() : DatatypeConstants.FIELD_UNDEFINED;
      if (defaultDay != DatatypeConstants.FIELD_UNDEFINED) {
        result.set(Calendar.DAY_OF_MONTH, defaultDay);
      }
    }

    // only set hour if it is set
    if (hour != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.HOUR_OF_DAY, hour);
    } else {
      // use default if set
      int defaultHour = (defaults != null) ? defaults.getHour() : DatatypeConstants.FIELD_UNDEFINED;
      if (defaultHour != DatatypeConstants.FIELD_UNDEFINED) {
        result.set(Calendar.HOUR_OF_DAY, defaultHour);
      }
    }

    // only set minute if it is set
    if (minute != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.MINUTE, minute);
    } else {
      // use default if set
      int defaultMinute =
          (defaults != null) ? defaults.getMinute() : DatatypeConstants.FIELD_UNDEFINED;
      if (defaultMinute != DatatypeConstants.FIELD_UNDEFINED) {
        result.set(Calendar.MINUTE, defaultMinute);
      }
    }

    // only set second if it is set
    if (second != DatatypeConstants.FIELD_UNDEFINED) {
      result.set(Calendar.SECOND, second);
    } else {
      // use default if set
      int defaultSecond =
          (defaults != null) ? defaults.getSecond() : DatatypeConstants.FIELD_UNDEFINED;
      if (defaultSecond != DatatypeConstants.FIELD_UNDEFINED) {
        result.set(Calendar.SECOND, defaultSecond);
      }
    }

    // only set millisend if it is set
    if (fractionalSecond != null) {
      result.set(Calendar.MILLISECOND, getMillisecond());
    } else {
      // use default if set
      BigDecimal defaultFractionalSecond =
          (defaults != null) ? defaults.getFractionalSecond() : null;
      if (defaultFractionalSecond != null) {
        result.set(Calendar.MILLISECOND, defaults.getMillisecond());
      }
    }

    return result;
  }

  /**
   * <p>Returns a <code>java.util.TimeZone</code> for this class.</p>
   *
   * <p>If timezone field is defined for this instance,
   * returns TimeZone initialized with custom timezone id
   * of zoneoffset. If timezone field is undefined,
   * try the defaultZoneoffset that was passed in.
   * If defaultZoneoffset is DatatypeConstants.FIELD_UNDEFINED, return
   * default timezone for this host.
   * (Same default as java.util.GregorianCalendar).</p>
   *
   * @param defaultZoneoffset default zoneoffset if this zoneoffset is {@link
   * DatatypeConstants#FIELD_UNDEFINED}.
   * @return TimeZone for this.
   */
  public TimeZone getTimeZone(int defaultZoneoffset) {
    TimeZone result = null;
    int zoneoffset = getTimezone();

    if (zoneoffset == DatatypeConstants.FIELD_UNDEFINED) {
      zoneoffset = defaultZoneoffset;
    }
    if (zoneoffset == DatatypeConstants.FIELD_UNDEFINED) {
      result = TimeZone.getDefault();
    } else {
      // zoneoffset is in minutes. Convert to custom timezone id format.
      char sign = zoneoffset < 0 ? '-' : '+';
      if (sign == '-') {
        zoneoffset = -zoneoffset;
      }
      int hour = zoneoffset / 60;
      int minutes = zoneoffset - (hour * 60);

      // Javadoc for java.util.TimeZone documents max length
      // for customTimezoneId is 8 when optional ':' is not used.
      // Format is
      // "GMT" ('-'|''+') (digit digit?) (digit digit)?
      //                   hour          minutes
      StringBuffer customTimezoneId = new StringBuffer(8);
      customTimezoneId.append("GMT");
      customTimezoneId.append(sign);
      customTimezoneId.append(hour);
      if (minutes != 0) {
        customTimezoneId.append(minutes);
      }
      result = TimeZone.getTimeZone(customTimezoneId.toString());
    }
    return result;
  }

  /**
   * <p>Creates and returns a copy of this object.</p>
   *
   * @return copy of this <code>Object</code>
   */
  public Object clone() {
    // Both this.eon and this.fractionalSecond are instances
    // of immutable classes, so they do not need to be cloned.
    return new XMLGregorianCalendarImpl(getEonAndYear(),
        this.month, this.day,
        this.hour, this.minute, this.second,
        this.fractionalSecond,
        this.timezone);
  }

  /**
   * <p>Unset all fields to undefined.</p>
   *
   * <p>Set all int fields to {@link DatatypeConstants#FIELD_UNDEFINED} and reference fields
   * to null.</p>
   */
  public void clear() {
    eon = null;
    year = DatatypeConstants.FIELD_UNDEFINED;
    month = DatatypeConstants.FIELD_UNDEFINED;
    day = DatatypeConstants.FIELD_UNDEFINED;
    timezone = DatatypeConstants.FIELD_UNDEFINED;  // in minutes
    hour = DatatypeConstants.FIELD_UNDEFINED;
    minute = DatatypeConstants.FIELD_UNDEFINED;
    second = DatatypeConstants.FIELD_UNDEFINED;
    fractionalSecond = null;
  }

  public void setMillisecond(int millisecond) {
    if (millisecond == DatatypeConstants.FIELD_UNDEFINED) {
      fractionalSecond = null;
    } else {
      if (millisecond < 0 || 999 < millisecond) {
        if (millisecond != DatatypeConstants.FIELD_UNDEFINED) {
          invalidFieldValue(MILLISECOND, millisecond);
        }
      }
      fractionalSecond = new BigDecimal((long) millisecond).movePointLeft(3);
    }
  }

  public void setFractionalSecond(BigDecimal fractional) {
    if (fractional != null) {
      if ((fractional.compareTo(DECIMAL_ZERO) < 0) ||
          (fractional.compareTo(DECIMAL_ONE) > 0)) {
        throw new IllegalArgumentException(DatatypeMessageFormatter.formatMessage(null,
            "InvalidFractional", new Object[]{fractional}));
      }
    }
    this.fractionalSecond = fractional;
  }

  private final class Parser {

    private final String format;
    private final String value;

    private final int flen;
    private final int vlen;

    private int fidx;
    private int vidx;

    private Parser(String format, String value) {
      this.format = format;
      this.value = value;
      this.flen = format.length();
      this.vlen = value.length();
    }

    /**
     * <p>Parse a formated <code>String</code> into an <code>XMLGregorianCalendar</code>.</p>
     *
     * <p>If <code>String</code> is not formated as a legal <code>XMLGregorianCalendar</code> value,
     * an <code>IllegalArgumentException</code> is thrown.</p>
     *
     * @throws IllegalArgumentException If <code>String</code> is not formated as a legal
     * <code>XMLGregorianCalendar</code> value.
     */
    public void parse() throws IllegalArgumentException {
      while (fidx < flen) {
        char fch = format.charAt(fidx++);

        if (fch != '%') { // not a meta character
          skip(fch);
          continue;
        }

        // seen meta character. we don't do error check against the format
        switch (format.charAt(fidx++)) {
          case 'Y': // year
            parseAndSetYear(4);
            break;

          case 'M': // month
            setMonth(parseInt(2, 2));
            break;

          case 'D': // days
            setDay(parseInt(2, 2));
            break;

          case 'h': // hours
            setHour(parseInt(2, 2), false);
            break;

          case 'm': // minutes
            setMinute(parseInt(2, 2));
            break;

          case 's': // parse seconds.
            setSecond(parseInt(2, 2));

            if (peek() == '.') {
              setFractionalSecond(parseBigDecimal());
            }
            break;

          case 'z': // time zone. missing, 'Z', or [+-]nn:nn
            char vch = peek();
            if (vch == 'Z') {
              vidx++;
              setTimezone(0);
            } else if (vch == '+' || vch == '-') {
              vidx++;
              int h = parseInt(2, 2);
              skip(':');
              int m = parseInt(2, 2);
              setTimezone((h * 60 + m) * (vch == '+' ? 1 : -1));
            }

            break;

          default:
            // illegal meta character. impossible.
            throw new InternalError();
        }
      }

      if (vidx != vlen) {
        // some tokens are left in the input
        throw new IllegalArgumentException(value); //,vidx);
      }
      testHour();
    }

    private char peek() throws IllegalArgumentException {
      if (vidx == vlen) {
        return (char) -1;
      }
      return value.charAt(vidx);
    }

    private char read() throws IllegalArgumentException {
      if (vidx == vlen) {
        throw new IllegalArgumentException(value); //,vidx);
      }
      return value.charAt(vidx++);
    }

    private void skip(char ch) throws IllegalArgumentException {
      if (read() != ch) {
        throw new IllegalArgumentException(value); //,vidx-1);
      }
    }

    private int parseInt(int minDigits, int maxDigits)
        throws IllegalArgumentException {

      int n = 0;
      char ch;
      int vstart = vidx;
      while (isDigit(ch = peek()) && (vidx - vstart) <= maxDigits) {
        vidx++;
        n = n * 10 + ch - '0';
      }
      if ((vidx - vstart) < minDigits) {
        // we are expecting more digits
        throw new IllegalArgumentException(value); //,vidx);
      }

      return n;
    }

    private void parseAndSetYear(int minDigits)
        throws IllegalArgumentException {
      int vstart = vidx;
      int n = 0;
      boolean neg = false;

      // skip leading negative, if it exists
      if (peek() == '-') {
        vidx++;
        neg = true;
      }
      while (true) {
        char ch = peek();
        if (!isDigit(ch)) {
          break;
        }
        vidx++;
        n = n * 10 + ch - '0';
      }

      if ((vidx - vstart) < minDigits) {
        // we are expecting more digits
        throw new IllegalArgumentException(value); //,vidx);
      }

      if (vidx - vstart < 7) {
        // definitely int only. I don't know the exact # of digits that can be in int,
        // but as long as we can catch (0-9999) range, that should be enough.
        if (neg) {
          n = -n;
        }
        year = n;
        eon = null;
      } else {
        setYear(new BigInteger(value.substring(vstart, vidx)));
      }
    }

    private BigDecimal parseBigDecimal()
        throws IllegalArgumentException {
      int vstart = vidx;

      if (peek() == '.') {
        vidx++;
      } else {
        throw new IllegalArgumentException(value);
      }
      while (isDigit(peek())) {
        vidx++;
      }
      return new BigDecimal(value.substring(vstart, vidx));
    }
  }

  private static boolean isDigit(char ch) {
    return '0' <= ch && ch <= '9';
  }

  /**
   * Prints this object according to the format specification.
   *
   * <p>
   * I wrote a custom format method for a particular format string to
   * see if it improves the performance, but it didn't. So this interpreting
   * approach isn't too bad.
   *
   * <p>
   * StringBuffer -> StringBuilder change had a very visible impact.
   * It almost cut the execution time to half, but unfortunately we can't use it
   * because we need to run on JDK 1.3
   */
  private String format(String format) {
    char[] buf = new char[32];
    int bufPtr = 0;

    int fidx = 0, flen = format.length();

    while (fidx < flen) {
      char fch = format.charAt(fidx++);
      if (fch != '%') {// not a meta char
        buf[bufPtr++] = fch;
        continue;
      }

      switch (format.charAt(fidx++)) {
        case 'Y':
          if (eon == null) {
            // optimized path
            int y = getYear();
            if (y < 0) {
              buf[bufPtr++] = '-';
              y = -y;
            }
            bufPtr = print4Number(buf, bufPtr, y);
          } else {
            String s = getEonAndYear().toString();
            // reallocate the buffer now so that it has enough space
            char[] n = new char[buf.length + s.length()];
            System.arraycopy(buf, 0, n, 0, bufPtr);
            buf = n;
            for (int i = s.length(); i < 4; i++) {
              buf[bufPtr++] = '0';
            }
            s.getChars(0, s.length(), buf, bufPtr);
            bufPtr += s.length();
          }
          break;
        case 'M':
          bufPtr = print2Number(buf, bufPtr, getMonth());
          break;
        case 'D':
          bufPtr = print2Number(buf, bufPtr, getDay());
          break;
        case 'h':
          bufPtr = print2Number(buf, bufPtr, getHour());
          break;
        case 'm':
          bufPtr = print2Number(buf, bufPtr, getMinute());
          break;
        case 's':
          bufPtr = print2Number(buf, bufPtr, getSecond());
          if (getFractionalSecond() != null) {
            // Note: toPlainString() isn't available before Java 1.5
            String frac = getFractionalSecond().toString();

            int pos = frac.indexOf("E-");
            if (pos >= 0) {
              String zeros = frac.substring(pos + 2);
              frac = frac.substring(0, pos);
              pos = frac.indexOf(".");
              if (pos >= 0) {
                frac = frac.substring(0, pos) + frac.substring(pos + 1);
              }
              int count = Integer.parseInt(zeros);
              if (count < 40) {
                frac = "00000000000000000000000000000000000000000".substring(0, count - 1) + frac;
              } else {
                // do it the hard way
                while (count > 1) {
                  frac = "0" + frac;
                  count--;
                }
              }
              frac = "0." + frac;
            }

            // reallocate the buffer now so that it has enough space
            char[] n = new char[buf.length + frac.length()];
            System.arraycopy(buf, 0, n, 0, bufPtr);
            buf = n;
            //skip leading zero.
            frac.getChars(1, frac.length(), buf, bufPtr);
            bufPtr += frac.length() - 1;
          }
          break;
        case 'z':
          int offset = getTimezone();
          if (offset == 0) {
            buf[bufPtr++] = 'Z';
          } else if (offset != DatatypeConstants.FIELD_UNDEFINED) {
            if (offset < 0) {
              buf[bufPtr++] = '-';
              offset *= -1;
            } else {
              buf[bufPtr++] = '+';
            }
            bufPtr = print2Number(buf, bufPtr, offset / 60);
            buf[bufPtr++] = ':';
            bufPtr = print2Number(buf, bufPtr, offset % 60);
          }
          break;
        default:
          throw new InternalError();  // impossible
      }
    }

    return new String(buf, 0, bufPtr);
  }

  /**
   * Prints an int as two digits into the buffer.
   *
   * @param number Number to be printed. Must be positive.
   */
  private int print2Number(char[] out, int bufptr, int number) {
    out[bufptr++] = (char) ('0' + (number / 10));
    out[bufptr++] = (char) ('0' + (number % 10));
    return bufptr;
  }

  /**
   * Prints an int as four digits into the buffer.
   *
   * @param number Number to be printed. Must be positive.
   */
  private int print4Number(char[] out, int bufptr, int number) {
    out[bufptr + 3] = (char) ('0' + (number % 10));
    number /= 10;
    out[bufptr + 2] = (char) ('0' + (number % 10));
    number /= 10;
    out[bufptr + 1] = (char) ('0' + (number % 10));
    number /= 10;
    out[bufptr] = (char) ('0' + (number % 10));
    return bufptr + 4;
  }

  /**
   * Compute <code>value*signum</code> where value==null is treated as
   * value==0.
   *
   * @return non-null {@link BigInteger}.
   */
  static BigInteger sanitize(Number value, int signum) {
    if (signum == 0 || value == null) {
      return BigInteger.ZERO;
    }
    return (signum < 0) ? ((BigInteger) value).negate() : (BigInteger) value;
  }

  /**
   * <p><code>reset()</code> is designed to allow the reuse of existing
   * <code>XMLGregorianCalendar</code>s thus saving resources associated
   * with the creation of new <code>XMLGregorianCalendar</code>s.</p>
   */
  public void reset() {
    //PENDING : Implementation of reset method
  }
}
